<?php
/**     
  *
  * Copyright (c) 2009, Persistent Systems Limited
  *
  * Redistribution and use, with or without modification, are permitted 
  *  provided that the following  conditions are met:
  *   - Redistributions of source code must retain the above copyright notice, 
  *     this list of conditions and the following disclaimer.
  *   - Neither the name of Persistent Systems Limited nor the names of its contributors 
  *     may be used to endorse or promote products derived from this software 
  *     without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  
/**
  *
  * This file contains the definition of HttpBatchResponse Class
  */
  
/**
  * The HttpBatchResponse Class.
  */
  
class HttpBatchResponse
{
    protected $_httpResponses;
    protected $_rawHttpBatchResponse;
    protected $_correctHttpLine;
    
    /**
     *
     * @param <array of Microsoft_Http_Response> $httpResponses
     * @param <string> $rawHttpBatchResponse
     * @return <string> No Return value
     * Construct a HttpBatchResponse object
     */
    public function HttpBatchResponse($httpResponses, $rawHttpBatchResponse)
    {
        $this->_httpResponses = $httpResponses;
        $this->_rawHttpBatchResponse = $rawHttpBatchResponse;
        $this->_correctHttpLine = self::ExtractCorrectHttpLine($rawHttpBatchResponse);
    }
    
    /**
     *
     * @return <integer> HTTP Staus Code
     * HTTP Batch Response can be in one of the two formats
     *	1. HTTP/1.1 100 Continue [newline][newline] HTTP/1.1 Code Message
     *     In this case the actual code is the code part of HTTP string in second line
     *	2. HTTP/1.1 Code Message
     */
    public function GetCode()
    {
        if($this->_correctHttpLine != null)
        {
            preg_match("|^HTTP/[\d\.x]+ (\d+)|", $this->_correctHttpLine, $m);
            if (isset($m[1])) { return (int)$m[1]; }
        }
        return false;
    }
    
    /**
     *
     * @return <integer> HTTP Staus Message
     * HTTP Batch Response can be in one of the two formats
     *	1. HTTP/1.1 100 Continue [newline][newline] HTTP/1.1 Code Message
     *     In this case the actual code is the Message part of HTTP string in second line
     *	2. HTTP/1.1 Code Message
     */
    public function GetMessage()
    {
        if($this->_correctHttpLine != null)
        {
            preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $this->_correctHttpLine, $m);
            if (isset($m[1])) { return $m[1]; }
        }
        return false;
    }
    
    /**
     *
     * @return <bool> true if batch header contains any error else false
     * Note that this is the error returned by webserver[IIS], not by 
     * data service. If any data service error happened then there will be
     * only one changeset and HTTP header of that changeset contains actual
     * error and HTTP status message of batch response will be 'Accepted'
     */
    public function IsError()
    {
        if($this->_correctHttpLine != null)
        {
            preg_match("|^HTTP/[\d\.x]+ (\d+)|", $this->_correctHttpLine, $m);
            if (isset($m[1]))
            {			
                $floorVal = floor((int)$m[1] / 100);
                return ($floorVal == 4 || $floorVal == 5);
            }
        }
        return false;
    }
    
    /**
     *
     * @return <array of Microsoft_Http_Response> 
     * Returns a list of Microsoft_Http_Response, each representing
     * a changeset
     */
    public function GetSubBatchHttpResponses()
    {
        return $this->_httpResponses;
    }
    
    /**
     *
     * @param <string> $httpBatchResponse
     * @param <HttpBatchResponse> 
     * Construct a HttpBatchResponse object from raw batch response
     */
    public static function Create($httpBatchResponse)
    {	
        $changesetBoundary = null;
        $httpResponses = array();
        if (!self::CheckIsError($httpBatchResponse))
        {
            $changesetBoundary = HttpBatchResponse::ExtractChangesetBoundary($httpBatchResponse);
            if (!isset($changesetBoundary))
            {
                throw new ADODotNETDataServicesException("Batch Response is Invalid. Missing changeset boundary value");
            }			
            $httpResponses = HttpBatchResponse::ExtractHttpResponses($httpBatchResponse, $changesetBoundary);	
        }
        return new HttpBatchResponse($httpResponses, $httpBatchResponse);
    }
    
    /**
     *
     * @param <string> $rawHttpBatchResponse
     * @return <bool> Returns true if batch header of $rawHttpBatchResponsecontains 
     * HTTP status code for error else false
     */
    protected static function CheckIsError($rawHttpBatchResponse)
    {
        $httpLine = self::ExtractCorrectHttpLine($rawHttpBatchResponse);
        if($httpLine != null)
        {
            preg_match("|^HTTP/[\d\.x]+ (\d+)|", $httpLine, $m);
            if (isset($m[1]))
            {
                $floorVal = floor((int)$m[1] / 100);
                return ($floorVal == 4 || $floorVal == 5);
            }
        }
        return false;
    }
    
    /**
     * @param <string> $rawHttpBatchResponse
     * @return <string> http line (ex: HTTP/1.1 202 Accepted)
     * 
     * This function will extract the final HTTP line. For example if Windows auth is
     * enabled then the http response will contains 3 http headers. First two headers
     * will be auth related and 3rd will be the actual IIS response specific to the 
     * request. We need to extract the 3rd one. Similarily in the case of Digest auth
     * the repsonse will contains one auth header and one header specific to the request
     */
    protected static function ExtractCorrectHttpLine($rawHttpBatchResponse)
    {
        if(preg_match_all ("|HTTP/[\d\.x]+ \d+ [^\r\n]+|", $rawHttpBatchResponse, $multiArray, PREG_OFFSET_CAPTURE))
        {
            if (isset($multiArray[0]))
            {
                if (!(isset($multiArray[0][0]) && isset($multiArray[0][0][0])))
                {
                    return null;
                }
                
                $prevHeader = $multiArray[0][0][0];
                $index = self::ExtractBatchBoundaryIndex($rawHttpBatchResponse);
                unset($multiArray[0][0]);
                //If BatchBoundry tag is not present, then return the last HTTP Line from the collection
                if($index == -1)
                {
                    $count = count($multiArray[0]);
                    if($count > 0)
                    {
                        $prevHeader = $multiArray[0][$count][0];
                    }
                }
                else
                {
                    foreach($multiArray[0] as $array)
                    {
                        if ($array[1] > $index)
                        {
                            break;
                        }
                        $prevHeader = $array[0];
                    }
                }
                return $prevHeader;
            }
        }
        return null;
    }
    
    /**
     *
     * @param <string> $rawHttpBatchResponse
     * @return <string> The changeset boundary if exists else null
     */
    protected static function ExtractChangesetBoundary($httpBatchResponse)
    {
        preg_match("|boundary=(changesetresponse_[^\r\n]+)|", $httpBatchResponse, $m);
        if (isset($m[1])) { return $m[1]; }
        return null;
    }
    
    /**
     *
     * @param <string> $rawHttpBatchResponse
     * @return <int> The index of batch boundary string
     */
    protected static function ExtractBatchBoundaryIndex($httpBatchResponse)
    {
        preg_match("|boundary=(batchresponse_[^\r\n]+)|", $httpBatchResponse, $m, PREG_OFFSET_CAPTURE);	
        if (isset($m[1]) && isset($m[1][1])) { return $m[1][1]; }
        return -1;
    }

    /**
     *
     * @param <string> $rawHttpBatchResponse
     * @param <string> changesetBoundary
     * @return <array of Microsoft_Http_Response> 
     * This function will extract the changesets from raw batch response and generate
     * a list of Microsoft_Http_Response objects. 
     */
    protected static function ExtractHttpResponses($httpBatchResponse, $changesetBoundary)
    {
        $httpResponses = array();
        $parts = explode( "--" . $changesetBoundary, $httpBatchResponse);	
        $count = count($parts);
        if($count < 2)
        {
            return $httpResponses;
        }
        unset($parts[0]);
        unset($parts[$count-1]);
        foreach ($parts as $changeSet)
        {
            $subParts = preg_split('|(?:\r?\n){2}|m', $changeSet, 2);
            $httpResponses[] = Microsoft_Http_Response::fromString($subParts[1]);	
        }
        return $httpResponses;
    }
}
?>